﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Management;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using WS.FlowAuth.Lib.DTO;
using ZXing;
using ZXing.Common;
using ZXing.QrCode;
using ZXing.QrCode.Internal;

namespace WS.FlowAuth.Lib
{
    public static class UserAuthCode
    {
        private static Random rand = new Random();
        private static string _publicKey = "";

        static UserAuthCode()
        {
            _publicKey =
                "<RSAKeyValue><Modulus>sr4C1OmHs6Rz/ND2t/dWRnO6fEjcczf0OHWDpjBMOL6pyUlsgsvXx6J/DAtiCKecAqwrfpibRzk9eu0LMZvYAEFar0LgLYwvZZL5uN0vBl6836Lfp6LNgdGJV/ika2Om8zLXGEU087XXMd7dlEYgs/41rFbOHvmWmX344VC/ric=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
        }

        public static DTO_QrCode GetQrCode(GetQrCodeArgs args)
        {
            //数据格式:  随机码|流量计序号|机器码|操作码|用户代码|操作数据

            if (string.IsNullOrWhiteSpace(args.flowNo))
            {
                throw new ArgumentNullException("args.flowNo");
            }

            if (string.IsNullOrWhiteSpace(args.machineCode))
            {
                throw new ArgumentNullException("args.machineCode");
            }

            if (string.IsNullOrWhiteSpace(args.optCode))
            {
                throw new ArgumentNullException("args.optCode");
            }

            if (string.IsNullOrWhiteSpace(args.userCode))
            {
                throw new ArgumentNullException("args.userCode");
            }

            if (args.optData == null)
            {
                throw new ArgumentNullException("args.optData");
            }

            if (string.IsNullOrWhiteSpace(args.host))
            {
                throw new ArgumentNullException("args.host");
            }

            var optDataStr = Convert.ToBase64String(args.optData);

            var flowNo = args.flowNo;
            var machineCode = args.machineCode;
            var optCode = args.optCode;
            var userCode = args.userCode;
            //var optData = args.optData;
            var host = args.host;

            var randomCode = rand.Next(0, 99999999);

            var data =
                $"{randomCode:D8}|{flowNo}|{machineCode}|{optCode}|{userCode}|{optDataStr}";

            var encData = EncryptData(_publicKey, data);

            var url = $"http://{host}?key={HttpUtility.UrlEncode(encData)}";

            EncodingOptions options = new QrCodeEncodingOptions
            {
                DisableECI = true,
                CharacterSet = "UTF-8",
                Width = 800,
                Height = 800,
                Margin = 2,
                ErrorCorrection = ErrorCorrectionLevel.M
            };

            BarcodeWriter writer = new BarcodeWriter();
            writer.Format = BarcodeFormat.QR_CODE;
            writer.Options = options;

            Bitmap bitmap = writer.Write(url);

            return new DTO_QrCode()
            {
                QrCodeImage = bitmap,
                RandomCode = randomCode.ToString("D8"),
                Url = url
            };
        }

        public static bool CheckAuthCode(string authCode, byte[] optData, string randomCode)
        {
            if (string.IsNullOrWhiteSpace(authCode))
            {
                throw new ArgumentOutOfRangeException("authCode");
            }

            if (string.IsNullOrWhiteSpace(randomCode))
            {
                throw new ArgumentOutOfRangeException("randomCode");
            }

            var optDataStr = Convert.ToBase64String(optData);

            var str = $"random={randomCode}|OptData={optDataStr}".MD5_32Array();

            if (str.JoinToString("", 0, str.Length, "D2").Substring(0, 8) == authCode)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private static string getHardDisk()
        {
            string HDInfo = "";
            ManagementClass cimobject1 = new ManagementClass("Win32_DiskDrive");
            ManagementObjectCollection moc1 = cimobject1.GetInstances();
            foreach (ManagementObject mo in moc1)
            {
                HDInfo = (string)mo.Properties["Model"].Value;
            }
            return HDInfo;
        }

        public static string EncryptData( string publicKey, string data,int encryptionBufferSize = 117, int decryptionBufferSize = 128)
        {
            using (var rsa = new RSACryptoServiceProvider())
            {
                rsa.FromXmlString(publicKey);
                byte[] dataEncoded = Encoding.UTF8.GetBytes(data);
                using (var ms = new MemoryStream())
                {
                    var buffer = new byte[encryptionBufferSize];
                    int pos = 0;
                    int copyLength = buffer.Length;
                    while (true)
                    {
                        if (pos + copyLength > dataEncoded.Length)
                        {
                            copyLength = dataEncoded.Length - pos;
                        }
                        buffer = new byte[copyLength];
                        Array.Copy(dataEncoded, pos, buffer, 0, copyLength);
                        pos += copyLength;
                        ms.Write(rsa.Encrypt(buffer, false), 0, decryptionBufferSize);
                        Array.Clear(buffer, 0, copyLength);
                        if (pos >= dataEncoded.Length)
                        {
                            break;
                        }

                    }

                    var res = Convert.ToBase64String(ms.ToArray());
                    return res;
                }

            }
        }

        //private static string RSAEncrypt(string publickey, string content)
        //{
        //    using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
        //    {
        //        byte[] cipherbytes;
        //        rsa.FromXmlString(publickey);
        //        cipherbytes = rsa.Encrypt(Encoding.UTF8.GetBytes(content), false);

        //        var result = Convert.ToBase64String(cipherbytes);

        //        //using (RSACryptoServiceProvider rds = new RSACryptoServiceProvider())
        //        //{
        //        //    rds.FromXmlString(_privateKey);

        //        //    var s = Convert.FromBase64String(result);

        //        //    var str1 = Encoding.UTF8.GetString(rds.Decrypt(s, false));
        //        //}

        //        return result;
        //    }


        //}

        private static string JoinToString(this byte[] n, string splite, int start, int length, string strformat)
        {
            if (n == null || n.Length <= 0 || start > n.Length || start < 0 || length <= 0 || start + length > n.Length)
            {
                return string.Empty;
            }

            var s = new StringBuilder(length * 2);

            for (int i = start; i < start + length; i++)
            {
                s.Append(n[i].ToString(strformat) + splite);
            }

            if (!string.IsNullOrWhiteSpace(splite))
            {
                s = s.Remove(s.Length - 1, splite.Length);
            }


            return s.ToString();
        }

        private static byte[] MD5_32Array(this string str)
        {
            byte[] s = null;

            using (MD5 m = new MD5CryptoServiceProvider())
            {
                s = m.ComputeHash(UnicodeEncoding.UTF8.GetBytes(str));
            }

            return s;
        }
    }

    public class GetQrCodeArgs
    {
        /// <summary>
        /// 服务器连接格式为: 地址:端口号
        /// </summary>
        public string host { set; get; }

        /// <summary>
        /// 用户代码
        /// </summary>
        public string userCode { set; get; }

        /// <summary>
        /// 机器码
        /// </summary>
        public string machineCode { set; get; }

        /// <summary>
        /// 流量计序号
        /// </summary>
        public string flowNo { set; get; }

        /// <summary>
        /// 操作代码
        /// </summary>
        public string optCode { set; get; }

        /// <summary>
        /// 操作数据
        /// </summary>
        public byte[] optData { set; get; }
    }
}
